#if !defined(MAY_NOT_MODIFY)
/****==========------------------------------------------------==========****/
/*									    */
/* tcpshow, v1.82							    */
/*									    */
/* Quickie to decode a "tcpdump" savefile.				    */
/*									    */
/* The application data is displayed as ASCII -- application protocols are  */
/* not decoded.								    */
/*									    */
/* The data captured by "tcpdump" might be less than in the original	    */
/* packet.  We kludge a solution to this with setjmp()/longjmp().	    */
/*									    */
/* Although written to read tcpdump savefiles, with tcpdump itself as a	    */
/* front-end, it'll decode any hex dump that adheres to the format	    */
/* expected.  Some programs which capture network data offer an option to   */
/* save the trace to a file in hex format -- this can often be massaged	    */
/* easily with Perl/awk/sh scripts to turn it into the format expected.	    */
/* As a special case, "tcpdump -s 1518 -lenx | tcpshow -cooked" works	    */
/* rather well, and "tcpdump -s 1518 -lenx | tcpshow -cooked -data" is nice */
/* for watching the data traffic in real time.				    */
/*									    */
/* ------------------------------------------------------------------------ */
/*									    */
/* Known Bugs:								    */
/* 1. If "-s n" wasn't specified with tcpdump, then when using the	    */
/*    "-nodata" flag, the "DATA: n bytes" line doesn't get output for the   */
/*    1st packet.							    */
/* 2. This file should be broken into several and a makefile written.  It   */
/*    should also be redesigned to allow new protocol decoders to be	    */
/*    plugged in easily.  Real soon now ;-)				    */
/*									    */
/* ------------------------------------------------------------------------ */
/*									    */
/* Copyright (c) 1996, 1997, 1998, 1999, 2000 Michael Ryan		    */
/* All rights reserved.							    */
/*									    */
/* This source code is owned and copyrighted by Michael Ryan.  This file    */
/* and all files derived from it, directly or indirectly (such files	    */
/* collectively and separately being referred to henceforth as "this file") */
/* may be used, modified and redistributed subject to the following six	    */
/* Conditions.								    */
/*									    */
/* Condition 1 of 6:							    */
/* That all text (code/comments, etc.) in this file surrounded by the macro */
/* block "#if !defined(MAY_NOT_MODIFY) ... #endif", including the macro	    */
/* statements themselves, may not be modified in any way, or deleted.  In   */
/* particular, this comment block and the printf() statements identifying   */
/* Michael Ryan as being the copyright owner, in the function usage(), may  */
/* not be modified or deleted.	The single, only, exception to this is that */
/* the non-inclusion of C comments by a C compiler/linker, in the object    */
/* and executable images it produces, is permitted.			    */
/*									    */
/* Condition 2 of 6:							    */
/* That no financial gain be made from using this file or modifying this    */
/* file.  It is permitted to charge for redistributing this file.	    */
/*									    */
/* Condition 3 of 6:							    */
/* That no conditions other than these six Conditions be applied to the	    */
/* use, modification or redistribution of this file.			    */
/*									    */
/* Condition 4 of 6:							    */
/* That all modifications to this file show prominently the name of the	    */
/* person that made the change and the date on which the change was made.   */
/*									    */
/* Condition 5 of 6:							    */
/* That Michael Ryan and everybody else in the world dead, living and yet   */
/* to be born, are hereby free from liability of all and every kind arising */
/* from the use of this file by anybody for any purpose.  This file comes   */
/* "as is" and all warranties, express or implied, are disclaimed.  As the  */
/* manual page for chat(1) says, "if it breaks, then you get to keep both   */
/* pieces".								    */
/*									    */
/* Condition 6 of 6:							    */
/* That Michael Ryan reserves the right to alter these Conditions at any    */
/* time without giving prior notice, such alterations to apply only to the  */
/* version current at the time of issue of the alterations and all later    */
/* versions, and such alterations to apply only to versions produced	    */
/* exclusively by him.							    */
/*									    */
/* Addendum to Copyright:						    */
/* I'm not a legal eagle and I worded the above notice off the top of my    */
/* head, so it may be full of holes, but the spirit of my intentions are    */
/* clear from reading it.  Please respect these intentions.		    */
/*									    */
/* Me too:								    */
/* If anybody makes significant improvements to this file, such as adding   */
/* decode support for DHCP or DNS traffic, or IP and TCP options, I would   */
/* appreciate it if they sent me a copy of their work (please send to	    */
/* Michael.Ryan@pobox.com).  Thanks.					    */
/*									    */
/* ------------------------------------------------------------------------ */
/*									    */
/* File layout is as follows:						    */
/* system includes							    */
/* local includes							    */
/* #define macros							    */
/* typedefs								    */
/* declarations of extern variables					    */
/* declarations of extern functions					    */
/* declarations of global variables					    */
/* declarations of global functions					    */
/* declarations of static variables					    */
/* declarations of static functions					    */
/* definitions of functions						    */
/* (all functions and variables are declared/defined in alphabetical order) */
/*									    */
/* ------------------------------------------------------------------------ */
/*									    */
/* Compiles as follows:							    */
/* cc -s -O -o tcpshow tcpshow.c					    */
/*									    */
/* ------------------------------------------------------------------------ */
/*									    */
/* Who and when:							    */
/* MikeRyan, 11apr96.							    */
/* Email: Michael.Ryan@pobox.com					    */
/*									    */
/* ------------------------------------------------------------------------ */
/*									    */
/* Available from:							    */
/* ftp://ftp.NetworX.ie/pub/net/tcpshow/				    */
/*									    */
/* ------------------------------------------------------------------------ */
/*									    */
/* Modification History							    */
/* MikeRyan, 14may96: Allow "tcpdump" expressions to be passed in.	    */
/* MikeRyan, 15may96: Added UDP decode logic.				    */
/* MikeRyan, 16may96: Added ICMP decode logic.				    */
/* MikeRyan, 16may96: Added -b switch to break long lines		    */
/*			    -w option to specify the width of the page	    */
/*			    -h flag to give help on usage.		    */
/* MikeRyan, 28may96: Added -nolink					    */
/*			    -nodata					    */
/*			    -noip					    */
/*			    -track					    */
/*			    -terse					    */
/*			    -sb						    */
/*			    -s						    */
/* MikeRyan, 25jun96: Incorporated my "general.h" typedef's, so that the    */
/*		      present source file doesn't depend on any		    */
/*		      non-standard header files.  This allows it to be	    */
/*		      distributed as a single file.  Also included the	    */
/*		      copyright notice, as I'm making the program freely    */
/*		      available.					    */
/* MikeRyan, 26jun96: Added -cooked and -pp.				    */
/* MikeRyan, 26may97: Made "-terse" the default; added "-verbose".	    */
/* mr971010  v1.2	Reformatted layout to match my current preference   */
/*			(purely cosmetic change).			    */
/* mr971014  v1.3	Set width of page to 60 characters and turned on    */
/*			line wrap by default.  Changed "bflag" to "noBflag" */
/*			in the process.					    */
/* mr971021  v1.4   (a) Always show the "Packet n" line (it wasn't being    */
/*			displayed when "-data" was used).		    */
/*		    (b) Display the separator line (prsep()) before the 1st */
/*			packet (this is more consistent).		    */
/*		    (c) Added "-noPortNames" flag, to turn off mapping port */
/*			numbers into port names.  Now, port names are shown */
/*			except when "-noPortNames" is used.		    */
/*		    (d) Display short hostnames instead of IP addresses,    */
/*			unless "-noHostNames" is specified; display fully   */
/*			qualified hostnames if "-fqdn" is specified.	    */
/*			Function svcname() was renamed to portName() and    */
/*			enhanced a little during all this.		    */
/* mr971120  v1.5   (a) Added ARP/RARP decoding.			    */
/* mr980117  v1.6   (a) Added delta time for 2nd and subsequent packets.    */
/* mr980118  v1.7   (a) Corrected minor 'bug' in etherAddr().		    */
/*		    (b) Added -noEtherNames and the code to print Ethernet  */
/*			names (which happens by default).  This code is	    */
/*			presently particular to FreeBSD, so to leave it out */
/*			(so that Ethernet names are never displayed) define */
/*			the macro NOETHERNAMES.				    */
/* mr980129  v1.71  (a) Changed version from x.y to x.yz.		    */
/*		    (b) Added icmpExtras() to display the additional	    */
/*			information in some ICMP headers.		    */
/* mr980130  v1.72  (a) Added ETHER_PROTO_UNKNOWN to correct a bug in	    */
/*			showHdr().					    */
/* mr980202  v1.73  (a) Changed "-data" to "-minDecode", indicating a	    */
/*			minimal decode of the headers.	The change was made */
/*			because "tcpshow -data -nodata" had the potential   */
/*			for confusion!					    */
/*		    (b) Changed options like "-nodata" to "-noData".	    */
/*		    (c) Recoded etherProto() to generalise it a bit more.   */
/*			Also removed ETHER_PROTO_UNKNOWN as it's not needed */
/*			given the recoding of etherProto().		    */
/* mr980303  v1.74  (a) Don't display "-noEtherNames" as a run-time option  */
/*			if NOETHERNAMES is defined.			    */
/*		    (b) Changed "-minDecode" to "-minHdrDecode".	    */
/* mr990119  v1.75  (a) Display hostnames and ports on lines of their own,  */
/*			in terse output display format.			    */
/* mr990204  v1.76  (a) Display carriage returns as such instead of as	    */
/*			dots, in the ASCII dump of the packet payload.	    */
/*		    (b) Don't display a blank line after the last line in   */
/*			the ASCII decode.  Changed "noBflag" to "bFlag" in  */
/*			the process (see comment for mr971014 above).	    */
/*		    (c) Got rid of duplicate code concerned with the ASCII  */
/*			decode.						    */
/* mr990211  v1.77  (a) Show the timestamps and TCP flags when		    */
/*			"-minHdrDecode" is specified; changed the output    */
/*			format slightly in the process.			    */
/*		    (b) Renamed "-terse" to "-lessTerse", renamed	    */
/*			"-minHdrDecode" to "-terse" and left "-terse" as    */
/*			the default, meaning the "-minHdrDecode" output	    */
/*			format is now the default.			    */
/*		    (c) Cured minor bug in argv flag handling.		    */
/* mr990225  v1.78  (a) Changed format of TCP flags in terse mode.	    */
/*		    (b) Renamed "-terse" to "-terseHeaders", "-lessTerse"   */
/*			to "-compactHeaders" and "-verbose" to		    */
/*			"-verboseHeaders".				    */
/* mr990901  v1.79  (a) Added "-noTimestamp" flag and always display the    */
/*			arrival times of packets unless this option is	    */
/*			specified.					    */
/* mr000114  v1.80  (a) The version of tcpdump bundled with Red Hat Linux   */
/*			6.1 has been modified by the US Naval Research	    */
/*			Laboratory (NRL) in such a way that tcpshow didn't  */
/*			decode the Ethernet fields properly.  Code has been */
/*			added to try to recognise which tcpdump output	    */
/*			format we're dealing with.  Thanks to Steve Beaty   */
/*			<beatys@mscd.edu> for reporting this and to Chuck   */
/*			Phillips <cdp@peakpeak.com> for assisting.	    */
/* mr000117  v1.81  (a) Correction to the usage() help text.		    */
/* mn000321  v1.82  (a) Added decode support for IPIP.			    */
/*									    */
/****==========------------------------------------------------==========****/
#endif


#include <sys/types.h>		       // mr971021 Next four includes
#include <sys/socket.h>
#if !defined(NOETHERNAMES)
#include <net/ethernet.h>	       // mr980118 Particular to FreeBSD?
#endif
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <setjmp.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>


/* Some general defines.						    */
#if defined(FALSE)
#undef FALSE
#endif
#if defined(TRUE)
#undef TRUE
#endif
#define FALSE		(boolean)0
#define TRUE		(boolean)1
#define elif		   else if
#if !defined(reg)
#define reg		  register     /* For debugging purposes	    */
#endif
#define stricmp		strcasecmp     /* Case-insensitive string compare   */


#define VERSION		      1.82     /* Please change when appropriate    */
#define COOKER		 "tcpdump"
#define MAXCOOKARGS	       100     /* Max tcpdump expression words	    */

#define MAXPKT		     10240     /* Should be 1518 for Ethernet	    */
#define NCOLS			60     /* mr971014 Changed from 1024	    */
#define MAX_HOSTNAMELEN	       255     // mr971021
#define MAX_PORTNAMELEN		32     // mr971021
#define ETHER_ADDRLEN		17     // mr971120 Ether addr in ASCII
#define ETHER_MINADDRLEN	11     // mr000114 For example, "f:f:f:f:f:f"
#define IP_ADDRLEN		15     // mr971120 IP addr in ASCII

// Ethernet protocol types (add to this list as needed).
#define ETHER_PROTO_IP	    0x0800
#define ETHER_PROTO_ARP	    0x0806
#define ETHER_PROTO_RARP    0x8035
#define ETHER_PROTO_UNKNOWN 0x0000     // mr000114

// ARP/RARP header elements.
#define ARP_REQ			 1
#define ARP_RSP			 2
#define RARP_REQ		 3
#define RARP_RSP		 4
#define ARP_HW_ETHER		 1     // Hardware type is Ethernet
#define ARP_PROTO_IP	    0x0800     // Protocol type is IP

/* IP header elements.							    */
#define IPHDRLEN		20
#define FRAGOFF		    0x1FFF
#define MF		    0x2000
#define DF		    0x4000

/* TCP header elements.							    */
#define TCPHDRLEN		20
#define URG		    0x0020
#define ACK		    0x0010
#define PSH		    0x0008
#define RST		    0x0004
#define SYN		    0x0002
#define FIN		    0x0001
#define NTCPFLAGS		 6				/* mr990211 */

/* UDP header elements.							    */
#define UDPHDRLEN		 8

/* IP protocol types.							    */
#define IP			 0
#define ICMP			 1
#define IGMP			 2
#define GGP			 3
#define IPENCAP			 4
#define ST			 5
#define TCP			 6
#define EGP			 8
#define PUP			12
#define UDP			17
#define HMP			20
#define XNSIDP			22
#define RDP			27
#define ISOTP4			29
#define XTP			36
#define IDPRCMTP		39
#define RSVP			46
#define VMTP			81
#define OSPF			89
#define IPIP			94
#define ENCAP			98

/* ICMP types.								    */
#define ECHO_REPLY		 0
#define DST_UNREACH		 3
#define SRC_QUENCH		 4
#define REDIRECT		 5
#define ECHO_REQ		 8
#define ROUTER_AD		 9
#define ROUTER_SOL		10
#define TIME_EXCEED		11
#define PARAM_PROB		12
#define TIME_REQ		13
#define TIME_REPLY		14
#define INFO_REQ		15
#define INFO_REPLY		16
#define MASK_REQ		17
#define MASK_REPLY		18

/* ICMP codes for type == Destination Unreachable.			    */
#define NET_UNREACH		 0
#define HOST_UNREACH		 1
#define PROTO_UNREACH		 2
#define PORT_UNREACH		 3
#define DF_SET			 4
#define SRCROUTE_FAILED		 5
#define DSTNET_UNKNOWN		 6
#define DSTHOST_UNKNOWN		 7
#define SRCHOST_ISOLATED	 8
#define DSTNET_PROHIB		 9
#define DSTHOST_PROHIB		10
#define NET_UNREACH_TOS		11
#define HOST_UNREACH_TOS	12
#define COMM_PROHIB		13
#define HOST_PREC_VIOL		14
#define PREC_CUTOFF		15

/* ICMP codes for type == Redirect.					    */
#define REDIR_FOR_NET		 0
#define REDIR_FOR_HOST		 1
#define REDIR_FOR_TOSNET	 2
#define REDIR_FOR_TOSHOST	 3

/* ICMP codes for type == Time Exceeded.				    */
#define TTL_ZERO		 0
#define REASS_TIMEOUT		 1

/* ICMP codes for type == Parameter Problem.				    */
#define IP_HDR_BAD		 0
#define MISSING_OPT		 1

/* Skip remaining lines of current packet.  Note that this causes a	    */
/* longjmp(), so a succeeding "return" from a function isn't needed.	    */
#define nextPkt()   for (dataLen = 0; ; ) (void)getPkt()
/* Display a separator line between packet decodes.			    */
#define prSep() \
printf( \
   "--------------------------------------" \
   "-------------------------------------\n" \
)


/* My own preferred basic data types -- amend per target machine.	    */
typedef char boolean;
typedef float float4;
typedef double float8;
typedef char int1;
typedef short int2;
typedef int int4;
typedef unsigned char uint1;
typedef unsigned short uint2;
typedef unsigned int uint4;
typedef unsigned char uchar;


#if !defined(NOETHERNAMES)
// mr980118 ether_ntohost() and related functions aren't prototyped in the
// standard include directory.
extern struct ether_addr *ether_aton(char *);
extern int ether_ntohost(char *, struct ether_addr *);
#endif


void main(int, char **);


static boolean bFlag = FALSE;
static char *cookArgs[MAXCOOKARGS+1];
static boolean cookedFlag = FALSE;
static uint2 dataLen = 0;
static char *dfltCookArgs[] = {
   COOKER, "-enx", "-s10240", "-r-", (char *)NULL
};
static char dHostName[MAX_HOSTNAMELEN+1];
static char dIp[IP_ADDRLEN+1];	       // Destination IP address
static int etherType;		       // mr971120 Protocol encoded in frame
static boolean fqdnFlag = FALSE;       // mr971021
static jmp_buf jmpBuf;
static boolean compactFlag = FALSE;
static boolean noDataFlag = FALSE;
#if !defined(NOETHERNAMES)
static boolean noEtherNames = FALSE;   // mr980118
#else
static boolean noEtherNames = TRUE;    // mr980303
#endif
static boolean noIpFlag = TRUE;
static boolean noTimestamp = FALSE;    // mr990901
static boolean noLinkFlag = FALSE;
static boolean noHostNames = FALSE;    // mr971021
static boolean noPortNames = FALSE;    // mr971021
static int nPktsShown = 0;
static char *off = "off,";	       /* "off" in middle of list	    */
static char *off_e = "off";	       /* "off" at end of list		    */
static char *on = "on, ";	       /* "on" in middle of list	    */
static char *on_e = "on";	       /* "on" at end of list		    */
static int pageWidth = NCOLS;
static char *pkt;
static boolean ppFlag = FALSE;
static uint1 proto;
static boolean sFlag = FALSE;
static boolean sbFlag = FALSE;
static char sHostName[MAX_HOSTNAMELEN+1];
static char sIp[IP_ADDRLEN+1];	       // Source IP address
static boolean terseFlag = TRUE;
static boolean trackFlag = FALSE;
static char *unknown = "<unknown>";
static boolean verboseFlag = FALSE;    /* MikeRyan, 26may97		    */


static double canonTime(char *, double *, int *);
static char *deltaTime(double *, char *);
static void error(char *);
static char *etherAddr(char *, char **);
static char *etherName(char *, boolean);
static char *etherProto(char *, int *);
static void forkTcpdump(int, char **);
static uint1 getByte(char **);
static uint4 getLongWord(char **);
static char *getPkt(void);
static uint2 getWord(char **);
static char *hostName(char *, boolean);
static char *icmpCode(uint1, uint1);
static char *icmpExtras(uint1, uint1, char **, uint2 *);
static char *icmpType(uint1);
static char *ipAddr(char **);
static char *ipProto(uint1);
static char nextChar(char **);
static char *portName(uint2, char *, boolean);
static char *rmWSpace(char *);
static char *showArp(char *);
static char *showData(char *);
static char *showHdr(char *);
static char *showIcmp(char *);
static char *showIp(char *);
static void showPkt(char *);
static char *showRarp(char *);
static char *showTcp(char *);
static char *showTcpFlagsAsBits(uint2);				/* mr990211 */
static char *showTcpFlagsOn(uint2);				/* mr990225 */
static char *showUdp(char *);
static char *skip(char *, uint2);
static void usage(void);


/****==========------------------------------------------------==========****/
/*									    */
/* Return the canonical time (time in time units).			    */
/*									    */
/****==========------------------------------------------------==========****/

static double canonTime (char *dayTime, double *multiplier, int *multLen) {

   double time;


   time = (double)atoi(dayTime) * 60 * 60;
   dayTime += 3;

   time += (double)atoi(dayTime) * 60;
   dayTime += 3;

   time += (double)atoi(dayTime);
   dayTime += 3;

   // Time resolution differs between machines.	 We guess the resolution by
   // looking at the length of the fractional part of the time stamp.
   // Rather than using pow(), which requires the maths library, we just
   // run a simple loop to produce the same result.  This is only done once,
   // so efficiency doesn't matter.
   if (nPktsShown == 1) {
      int i;
      *multLen = strlen(dayTime);
      for (i = 0, *multiplier = 1; i < *multLen; ++i) *multiplier *= 10;
   }

   return time * *multiplier + (double)atol(dayTime);

}


/****==========------------------------------------------------==========****/
/*									    */
/* Return the time difference between this and the previous packet.	    */
/*									    */
/****==========------------------------------------------------==========****/

static char *deltaTime (double *prevTime, char *time) {

   double currTime;
   double delta;
   static char deltaString[32];
   int hours;
   int mins;
   static double multiplier;
   static int multLen;		       // Length of multiplier in chars
   char *s;
   int secs;


   // The timestamp may not be a valid time (e.g. if tcpdump isn't producing
   // the input but, rather, some end-user script).  We need to increase the
   // strength of this validation someday :-)
   if (!isdigit(*time) || time[2] != ':' || time[5] != ':') return "";

   currTime = canonTime(time, &multiplier, &multLen);

   if (nPktsShown == 1) {
      *prevTime = currTime;	       // Initialise on 1st packet
      return "";
   }

   delta = currTime - *prevTime;
   *prevTime = currTime;

   // Convert the delta time to daytime representation.
   // The subtractions from 'delta' simulate the % operator.  (Note: don't
   // change the value of 'multiplier', as it's set only on the 1st call to
   // canonTime()).
   hours = (int)(delta / (multiplier * 60 * 60));
   delta -= (double)hours * multiplier * 60 * 60;
   mins = (int)(delta / (multiplier * 60));
   delta -= (double)mins * multiplier * 60;
   secs = (int)(delta / multiplier);
   delta -= (double)secs * multiplier;
   (void)strcpy(deltaString, " (");
   s = &deltaString[2];
   (void)sprintf(s, "%02d:", hours);
   s += 3;
   (void)sprintf(s, "%02d:", mins);
   s += 3;
   (void)sprintf(s, "%d.", secs);
   while (*s) ++s;
   (void)sprintf(s, "%0*d", multLen, (int)delta);
   while (*s) ++s;
   *s++ = ')';
   *s = '\0';

   // Remove the hours and mins components if they're zero.
   s = deltaString + 2;		       // Skip over " ("
   if ((hours=atoi(s)) == 0) {
      s += 3;
      if ((mins=atoi(s)) == 0) {
	 s += 3;
      }
   }
   bcopy(s, deltaString+2, strlen(s)+1);

   return deltaString;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Print an error message and exit.					    */
/*									    */
/****==========------------------------------------------------==========****/

static void error (char *msg) {

   fprintf(stderr, "***Error: %s\n", msg);
   exit(1);

}


/****==========------------------------------------------------==========****/
/*									    */
/* Print a formatted Ethernet address.					    */
/* If pPkt is non-zero, we read the address from the packet stream in raw   */
/* hex format.								    */
/*									    */
/****==========------------------------------------------------==========****/

static char *etherAddr (char *eAddr, char **pPkt) {

   uint1 byte;
   static char formatted[ETHER_ADDRLEN+1];
   int i;
   int j;


   // If non-zero, read the hex bytes from the packet stream.
   if (pPkt) {
      byte = getByte(pPkt); sprintf(formatted+0,  "%02X:", byte);
      byte = getByte(pPkt); sprintf(formatted+3,  "%02X:", byte);
      byte = getByte(pPkt); sprintf(formatted+6,  "%02X:", byte);
      byte = getByte(pPkt); sprintf(formatted+9,  "%02X:", byte);
      byte = getByte(pPkt); sprintf(formatted+12, "%02X:", byte);
      byte = getByte(pPkt); sprintf(formatted+15, "%02X",  byte);
   }
   else {
      // The address is already in ASCII form; just pad out.
      for (i = j = 0; i < 6; i++)
	 if (eAddr[1] == ':' || eAddr[1] == '\0') {   // mr980118
	    formatted[j++] = '0';
	    formatted[j++] = toupper(eAddr[0]);
	    formatted[j++] = ':';
	    eAddr += 2;
	 }
	 else {
	    formatted[j++] = toupper(eAddr[0]);
	    formatted[j++] = toupper(eAddr[1]);
	    formatted[j++] = ':';
	    eAddr += 3;
	 }
      formatted[j-1] = '\0';
   }

   return formatted;

}


/****==========------------------------------------------------==========****/
/*									    */
/* If noEtherNames is true, then if returnEtherAddr is true return the	    */
/*    Ethernet address, otherwise return the value of 'unknown';	    */
/* otherwise, if there is no matching Ethernet name, then if		    */
/*    returnEtherAddr is true, return the Ethernet address, otherwise	    */
/*    return 'unknown';							    */
/* otherwise, return the Ethernet name.					    */
/*									    */
/* CURRENTLY, THIS FUNCTION USES LIBRARY CALLS PARTICULAR TO FREEBSD 2.1+.  */
/*									    */
/*									    */
/* mr980118								    */
/*									    */
/****==========------------------------------------------------==========****/

static char *etherName (char *etherAddr, boolean returnEtherAddr) {

#if defined(NOETHERNAMES)
   return returnEtherAddr? etherAddr: "no name";
#else
   struct ether_addr *e;
   static char name[MAX_HOSTNAMELEN+1];


   if (noEtherNames) return returnEtherAddr? etherAddr: unknown;

   if (!(e=ether_aton(etherAddr))) error("Badly formatted Ethernet address");

   if (ether_ntohost(name, e) != 0)
      return returnEtherAddr? etherAddr: unknown;

   return name;
#endif

}


/****==========------------------------------------------------==========****/
/*									    */
/* Return the type of protocol encapsulated in the Ethernet frame and also  */
/* a string representing that value.					    */
/*									    */
/****==========------------------------------------------------==========****/

static char *etherProto (char *typeStr, int *pType) {

   char *arp = "ARP";
   char *ip = "IP";
   char *rarp = "RARP";
   int type;

   /* mr000114 Ethernet type may be a hex number (e.g. "0800") or name	    */
   /* (e.g. "ip").							    */
   if (isxdigit(*typeStr))
      (void)sscanf(typeStr, "%x", &type);
   elif (stricmp(typeStr, ip) == 0)
      type = ETHER_PROTO_IP;
   elif (stricmp(typeStr, arp) == 0)
      type = ETHER_PROTO_ARP;
   elif (stricmp(typeStr, rarp) == 0)
      type = ETHER_PROTO_RARP;
   else
      type = ETHER_PROTO_UNKNOWN;

   if (pType) *pType = type;
   if (type == ETHER_PROTO_IP)
      return ip;
   elif (type == ETHER_PROTO_ARP)
      return arp;
   elif (type == ETHER_PROTO_RARP)
      return rarp;
   else
      return typeStr;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Run tcpdump to pre-process the trace file.				    */
/*									    */
/****==========------------------------------------------------==========****/

static void forkTcpdump (int argc, char **argv) {

   int fd[2];
   int i;
   pid_t pid;


   /* Required "tcpdump" flags.						    */
   i = 0;
   while (dfltCookArgs[i]) {
      cookArgs[i] = dfltCookArgs[i];
      i++;
   }
   while (argc-- > 0) {
      if (i >= MAXCOOKARGS) error("Too many expressions");
      cookArgs[i++] = *argv++;
   }
   cookArgs[i] = (char *)NULL;

   /* Fork tcpdump to cook our input.					    */
   if (pipe(fd)) error("pipe() failed");
   if ((pid=fork()) < 0) error("fork() failed");
   if (pid == 0) {
      (void)close(1);
      if (dup(fd[1]) != 1) error("dup() failed");
      (void)close(fd[0]);
      (void)close(fd[1]);
      execvp(COOKER, cookArgs);
      error("execvp() failed");
   }

   (void)close(0);
   if (dup(fd[0]) != 0) error("dup() failed");
   (void)close(fd[0]);
   (void)close(fd[1]);

}


/****==========------------------------------------------------==========****/
/*									    */
/* Return the byte value and increment the pointer by sizeof(byte).	    */
/*									    */
/****==========------------------------------------------------==========****/

static uint1 getByte (char **pPkt) {

   char byte[1*2+1];		       /* ASCII representation of a byte    */
   unsigned int val;


   byte[0] = nextChar(pPkt);
   byte[1] = nextChar(pPkt);
   byte[2] = '\0';

   (void)sscanf(byte, "%x", &val);

   return (uint1)val;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Return the longword value and increment the pointer by sizeof(longWord). */
/*									    */
/****==========------------------------------------------------==========****/

static uint4 getLongWord (char **pPkt) {

   char longWord[4*2+1];	      /* ASCII representation of a longword */
   unsigned long val;


   longWord[0] = nextChar(pPkt);
   longWord[1] = nextChar(pPkt);
   longWord[2] = nextChar(pPkt);
   longWord[3] = nextChar(pPkt);
   longWord[4] = nextChar(pPkt);
   longWord[5] = nextChar(pPkt);
   longWord[6] = nextChar(pPkt);
   longWord[7] = nextChar(pPkt);
   longWord[8] = '\0';

   (void)sscanf(longWord, "%lx", &val);

   return (uint4)val;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Read in the next line of packet data.				    */
/*									    */
/* Set global pointer "pkt" to start of buffer containing the packet line.  */
/*									    */
/****==========------------------------------------------------==========****/

static char *getPkt () {

   static boolean beenHereAlready = FALSE;
   static char pktBuf[MAXPKT+1];


   if (fgets(pktBuf, MAXPKT+1, stdin) == (char *)NULL) {
      if (nPktsShown > 0) prSep();
      exit(0);
   }

   /* Line without leading <tab> means start of new packet.		    */
   if (*pktBuf == '\t')
      return pkt = rmWSpace(pktBuf);
   elif (!beenHereAlready) {	       /* setjmp() won't have been called   */
      beenHereAlready = TRUE;	       /*  before reading 1st packet	    */
      return pkt = pktBuf;
   }
   else {
      if (dataLen > 0)
	 printf("\n\t<*** Rest of data missing from packet dump ***>\n");
      pkt = pktBuf;
      longjmp(jmpBuf, 1);
   }

}


/****==========------------------------------------------------==========****/
/*									    */
/* Return the word value and increment the pointer by sizeof(word).	    */
/*									    */
/****==========------------------------------------------------==========****/

static uint2 getWord (char **pPkt) {

   char word[2*2+1];		       /* ASCII representation of a word    */
   unsigned int val;


   word[0] = nextChar(pPkt);
   word[1] = nextChar(pPkt);
   word[2] = nextChar(pPkt);
   word[3] = nextChar(pPkt);
   word[4] = '\0';

   (void)sscanf(word, "%x", &val);

   return (uint2)val;

}


/****==========------------------------------------------------==========****/
/*									    */
/* If noHostNames is true, then if returnIpAddr is true return the IP	    */
/*    address, otherwise return the value of 'unknown';			    */
/* otherwise, if there is no matching hostname, then if returnIpAddr is	    */
/*    true, return the IP address, otherwise return 'unknown';		    */
/* otherwise, if fqdnFlag is true, return the fully-qualified hostname;	    */
/* otherwise, return the short hostname.				    */
/*									    */
/*									    */
/* mr971021								    */
/*									    */
/****==========------------------------------------------------==========****/

static char *hostName (char *ipAddr, boolean returnIpAddr) {

   struct hostent *h;
   static char name[MAX_HOSTNAMELEN+1];
   struct in_addr inAddr;
   char *s;


   if (noHostNames) return returnIpAddr? ipAddr: unknown;

   inAddr.s_addr = inet_addr(ipAddr);
   if (!(h = gethostbyaddr((char *)&inAddr, sizeof(inAddr), AF_INET)))
      return returnIpAddr? ipAddr: unknown;
   if (strlen(h->h_name) > MAX_HOSTNAMELEN) error("Hostname too long");
   (void)strcpy(name, h->h_name);

   if (!fqdnFlag) {
      s = name;
      while (*s && *s != '.') ++s;
      *s = '\0';
   }

   return name;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Print the code relating to the ICMP type.				    */
/*									    */
/****==========------------------------------------------------==========****/

static char *icmpCode (uint1 type, uint1 code) {

   char *bad;
   char *descr;


   bad = "<*** CORRUPT ***>";
   descr = (char *)NULL;

   switch (type) {
    case ECHO_REPLY:
      switch (code) {
       case 0:								break;
       default:		       descr = bad;				break;
      }
      break;
    case DST_UNREACH:
      switch (code) {
       case NET_UNREACH:       descr = "network-unreachable";		break;
       case HOST_UNREACH:      descr = "host-unreachable";		break;
       case PROTO_UNREACH:     descr = "protocol-unreachable";		break;
       case PORT_UNREACH:      descr = "port-unreachable";		break;
       case DF_SET:	       descr = "frag-needed-but-DF-set";	break;
       case SRCROUTE_FAILED:   descr = "source-route-failed";		break;
       case DSTNET_UNKNOWN:    descr = "destination-network-unknown";	break;
       case DSTHOST_UNKNOWN:   descr = "destination-host-unknown";	break;
       case SRCHOST_ISOLATED:  descr = "source-host-isolated";		break;
       case DSTNET_PROHIB:     descr = "dest-net-admin-prohibited";	break;
       case DSTHOST_PROHIB:    descr = "dest-host-admin-prohibited";	break;
       case NET_UNREACH_TOS:   descr = "network-unreachable-for-TOS";	break;
       case HOST_UNREACH_TOS:  descr = "host-unreachable-for-TOS";	break;
       case COMM_PROHIB:       descr = "trafffic-prohibited-by-filter"; break;
       case HOST_PREC_VIOL:    descr = "host-precedence-violation";	break;
       case PREC_CUTOFF:       descr = "precedence-cutoff-in-effect";	break;
       default:		       descr = bad;				break;
      }
      break;
    case SRC_QUENCH:
      switch (code) {
       case 0:								break;
       default:		       descr = bad;				break;
      }
      break;
    case REDIRECT:
      switch (code) {
       case REDIR_FOR_NET:     descr = "route-wrong-for-network";	break;
       case REDIR_FOR_HOST:    descr = "route-wrong-for-host";		break;
       case REDIR_FOR_TOSNET:  descr = "route-wrong-for-TOS-and-net";	break;
       case REDIR_FOR_TOSHOST: descr = "route-wrong-for-TOS-and-host";	break;
       default:		       descr = bad;				break;
      }
      break;
    case ECHO_REQ:
      switch (code) {
       case 0:								break;
       default:		       descr = bad;				break;
      }
      break;
    case ROUTER_AD:
      switch (code) {
       case 0:								break;
       default:		       descr = bad;				break;
      }
      break;
    case ROUTER_SOL:
      switch (code) {
       case 0:								break;
       default:		       descr = bad;				break;
      }
      break;
    case TIME_EXCEED:
      switch (code) {
       case TTL_ZERO:	       descr = "TTL-reached-zero";		break;
       case REASS_TIMEOUT:     descr = "reassembly-timer-expired";	break;
       default:		       descr = bad;				break;
      }
      break;
    case PARAM_PROB:
      switch (code) {
       case IP_HDR_BAD:	       descr = "IP-header-bad";			break;
       case MISSING_OPT:       descr = "required-option-is-missing";	break;
       default:		       descr = bad;				break;
      }
      break;
    case TIME_REQ:
      switch (code) {
       case 0:								break;
       default:		       descr = bad;				break;
      }
      break;
    case TIME_REPLY:
      switch (code) {
       case 0:								break;
       default:		       descr = bad;				break;
      }
      break;
    case INFO_REQ:
      switch (code) {
       case 0:								break;
       default:		       descr = bad;				break;
      }
      break;
    case INFO_REPLY:
      switch (code) {
       case 0:								break;
       default:		       descr = bad;				break;
      }
      break;
    case MASK_REQ:
      switch (code) {
       case 0:								break;
       default:		       descr = bad;				break;
      }
      break;
    case MASK_REPLY:
      switch (code) {
       case 0:								break;
       default:		       descr = bad;				break;
      }
      break;
    default:
      break;
   }

   return descr;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Some ICMP messages contain additional information.			    */
/* Return this information if it's available, NULL otherwise.		    */
/*									    */
/* On entry, *pPkt points at the 1st byte following the ICMP checksum.	    */
/*									    */
/****==========------------------------------------------------==========****/

static char *icmpExtras (
   uint1 type, uint1 code, char **pPkt, uint2 *nSkipped
) {

   char *dfSetText1 = "My next-hop MTU is ";
   uint2 dstPort;
   boolean haveInfo;
#if defined(INFOBUF)
#error "INFOBUF already defined"
#endif
#define INFOBUF 255
   static char info[INFOBUF+1];
   int ipHdrLen;
   uint2 mtu;
   char *portUnreachText1 = "Port ";
   char *portUnreachText2 = " is unreachable";
   char *redirectText1 = "Use router ";
   char *redirectText2 = " instead";
   char *s;
   uint1 transportProto;


   haveInfo = FALSE;

   // We're only interested in the ones that have extra information.
   switch (type) {
    case DST_UNREACH:
      switch (code) {
       case PORT_UNREACH:
	 *pPkt = skip(*pPkt, 4); *nSkipped += 4;   // Skip over MBZ field
	 ipHdrLen = (int)getByte(pPkt); *nSkipped += 1;
	 ipHdrLen = (ipHdrLen & 0x0F) * 4;
	 *pPkt = skip(*pPkt, 8); *nSkipped += 8;   // Skip to protocol field
	 transportProto = getByte(pPkt); *nSkipped += 1;
	 // Subtract ten because we've already processed these bytes.
	 *pPkt = skip(*pPkt, ipHdrLen-1-1-8); *nSkipped += ipHdrLen-1-1-8;
	 *pPkt = skip(*pPkt, 2); *nSkipped += 2;   // Skip past source port
	 dstPort = getWord(pPkt); *nSkipped += sizeof dstPort;
	 switch (transportProto) {
	  case TCP: s = "tcp"; break;
	  case UDP: s = "udp"; break;
	  default:  error("IP protocol field != TCP or UDP");
	 }
	 s = portName(dstPort, s, TRUE);
	 if (strlen(portUnreachText1)+strlen(portUnreachText2)+strlen(s) >
	     INFOBUF) error("INFOBUF too small");
	 (void)strcpy(info, portUnreachText1);
	 (void)strcat(info, s);
	 (void)strcat(info, portUnreachText2);
	 haveInfo = TRUE;
	 break;
       case DF_SET:
	 // If the router sending the ICMP is properly playing its part in
	 // PMTU discovery, the high word will contain the next-hop MTU.
	 *pPkt = skip(*pPkt, 2); *nSkipped += 2;
	 mtu = getWord(pPkt); *nSkipped += sizeof(mtu);
	 if (mtu) {
	    (void)strcpy(info, dfSetText1);
	    (void)sprintf(info+strlen(info), "%d", mtu);
	    haveInfo = TRUE;
	 }
	 break;
      }
      break;
    case REDIRECT:
      s = hostName(ipAddr(pPkt), TRUE); *nSkipped += 4;
      if (strlen(redirectText1)+strlen(redirectText2)+strlen(s) > INFOBUF)
	 error("INFOBUF too small");
      (void)strcpy(info, redirectText1);
      (void)strcat(info, s);
      (void)strcat(info, redirectText2);
      haveInfo = TRUE;
      break;
    case TIME_REQ:
      break;
    case TIME_REPLY:
      break;
    case INFO_REQ:
      break;
    case INFO_REPLY:
      break;
    case MASK_REQ:
      break;
    case MASK_REPLY:
      break;
   }

   return haveInfo? info: (char *)NULL;
#undef INFOBUF

}


/****==========------------------------------------------------==========****/
/*									    */
/* Print the type of ICMP packet.					    */
/*									    */
/****==========------------------------------------------------==========****/

static char *icmpType (uint1 type) {

   char *descr;


   switch (type) {
    case ECHO_REPLY:  descr = "echo-reply";		 break;
    case DST_UNREACH: descr = "destination-unreachable"; break;
    case SRC_QUENCH:  descr = "source-quench";		 break;
    case REDIRECT:    descr = "redirect";		 break;
    case ECHO_REQ:    descr = "echo-request";		 break;
    case ROUTER_AD:   descr = "router-advertisement";	 break;
    case ROUTER_SOL:  descr = "router-solicitation";	 break;
    case TIME_EXCEED: descr = "time-exceeded";		 break;
    case PARAM_PROB:  descr = "parameter-problem";	 break;
    case TIME_REQ:    descr = "timestamp-request";	 break;
    case TIME_REPLY:  descr = "timestamp-reply";	 break;
    case INFO_REQ:    descr = "information-request";	 break;
    case INFO_REPLY:  descr = "information-reply";	 break;
    case MASK_REQ:    descr = "address-mask-request";	 break;
    case MASK_REPLY:  descr = "address-mask-reply";	 break;
    default:	      descr = unknown;			 break;
   }

   return descr;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Print the IP address in dotted-quad.					    */
/*									    */
/****==========------------------------------------------------==========****/

static char *ipAddr (char **pPkt) {

   static char addr[IP_ADDRLEN+1];
   uint2 byte1;
   uint2 byte2;
   uint2 byte3;
   uint2 byte4;


   /* We don't use inet_ntoa() because it wants a socket structure.	    */
   byte1 = (uint2)getByte(pPkt);
   byte2 = (uint2)getByte(pPkt);
   byte3 = (uint2)getByte(pPkt);
   byte4 = (uint2)getByte(pPkt);
   (void)sprintf(addr, "%d.%d.%d.%d", byte1, byte2, byte3, byte4);

   return addr;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Print the type of protocol encapsulated in the IP datagram.		    */
/*									    */
/****==========------------------------------------------------==========****/

static char *ipProto (uint1 code) {

   char *name;


   /* A simple table won't do, as the codes aren't contiguous.		     */
   switch (code) {
    case IP:
       name = "IP"; break;
    case ICMP:
       name = "ICMP"; break;
    case IGMP:
       name = "IGMP"; break;
    case GGP:
       name = "GGP"; break;
    case IPENCAP:
       name = "IPENCAP"; break;
    case ST:
       name = "ST"; break;
    case TCP:
       name = "TCP"; break;
    case EGP:
       name = "EGP"; break;
    case PUP:
       name = "PUP"; break;
    case UDP:
       name = "UDP"; break;
    case HMP:
       name = "HMP"; break;
    case XNSIDP:
       name = "XNSIDP"; break;
    case RDP:
       name = "RDP"; break;
    case ISOTP4:
       name = "ISOTP4"; break;
    case XTP:
       name = "XTP"; break;
    case IDPRCMTP:
       name = "IDPRCMTP"; break;
    case RSVP:
       name = "RSVP"; break;
    case VMTP:
       name = "VMTP"; break;
    case OSPF:
       name = "OSPF"; break;
    case IPIP:
       name = "IPIP"; break;
    case ENCAP:
       name = "ENCAP"; break;
    default:
       name = unknown; break;
   }

   return name;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Decode a "tcpdump" savefile.						    */
/*									    */
/****==========------------------------------------------------==========****/

void main (int argc, char **argv) {

   /* Command line options.						    */
   while (--argc > 0 && **++argv == '-')
      if (strcmp(*argv, "-s") == 0) sFlag = TRUE;
      elif (strcmp(*argv, "-b") == 0) bFlag = TRUE;
      elif (strcmp(*argv, "-sb") == 0) sbFlag = TRUE;
      elif (strcmp(*argv, "-terseHeaders") == 0) {
	 terseFlag = noIpFlag = TRUE;
	 verboseFlag = compactFlag = noLinkFlag = FALSE;
      }
      elif (strcmp(*argv, "-compactHeaders") == 0) {
	 compactFlag = TRUE;
	 verboseFlag = terseFlag = noLinkFlag = noIpFlag = FALSE;
      }
      elif (strcmp(*argv, "-verboseHeaders") == 0) {
	 verboseFlag = TRUE;
	 compactFlag = terseFlag = noLinkFlag = noIpFlag = FALSE;
      }
      elif (strcmp(*argv, "-track") == 0) trackFlag = TRUE;
      elif (strcmp(*argv, "-noData") == 0) noDataFlag = TRUE;
      elif (strcmp(*argv, "-noTimestamp") == 0) noTimestamp = TRUE;
      elif (strcmp(*argv, "-noLink") == 0) noLinkFlag = TRUE;
      elif (strcmp(*argv, "-noIp") == 0) noIpFlag = TRUE;
      elif (strcmp(*argv, "-noHostNames") == 0) noHostNames = TRUE;
#if !defined(NOETHERNAMES)
      elif (strcmp(*argv, "-noEtherNames") == 0) noEtherNames = TRUE;
#endif
      elif (strcmp(*argv, "-noPortNames") == 0) noPortNames = TRUE;
      elif (strcmp(*argv, "-fqdn") == 0) fqdnFlag = TRUE;
      elif (strcmp(*argv, "-cooked") == 0) cookedFlag = TRUE;
      elif (strcmp(*argv, "-pp") == 0) ppFlag = TRUE;
      elif (strcmp(*argv, "-h") == 0) usage();
      elif (strcmp(*argv, "-w") == 0) {
	 if (--argc <= 0) error("-w needs a numeric argument");
	 if ((pageWidth=atoi(*++argv)) < 1) error("-w value too small");
      }
      else error("Unknown command line flag");

   if (!cookedFlag)
      forkTcpdump(argc, argv);
   elif (argc != 0)
      fprintf(stderr, "input is cooked -- ignoring tcpdump expressions\n");

   pkt = getPkt();
   for ( ; ; ) if (!setjmp(jmpBuf)) showPkt(pkt);

   exit(0);

}


/****==========------------------------------------------------==========****/
/*									    */
/* Return the next character in the packet buffer.			    */
/*									    */
/****==========------------------------------------------------==========****/

static char nextChar (char **pPkt) {

   if (!**pPkt) *pPkt = getPkt();

   return *(*pPkt)++;

}


/****==========------------------------------------------------==========****/
/*									    */
/* If noPortNames is true, then if wantNumber is true, return the port	    */
/*    number, otherwise return the value of 'unknown';			    */
/* otherwise, if the port has a name return that name;			    */
/* otherwise, if wantNumber is true return the port number;		    */
/* otherwise, return the value of 'unknown'.				    */
/*									    */
/****==========------------------------------------------------==========****/

static char *portName (uint2 port, char *proto, boolean wantNumber) {

   char *name;
   static char number[6];
   struct servent *service;	       /* Doesn't need to be static	    */


   // We could tighten this code up a little, but don't want to call
   // getservbyport() unless necessary.
   if (noPortNames)
      if (!wantNumber)
	 name = unknown;
      else {
	 (void)sprintf(number, "%d", port);
	 name = number;
      }
   /* The crappy manpage doesn't say the port must be in net byte order.    */
   elif (service = getservbyport((int)htons(port), proto))
      name = service->s_name;
   elif (!wantNumber)
      name = unknown;
   else {
      (void)sprintf(number, "%u", port);
      name = number;
   }

   if (strlen(name) > MAX_PORTNAMELEN) error("Port name too long");

   return name;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Remove whitespace from the buffer.					    */
/*									    */
/****==========------------------------------------------------==========****/

static char *rmWSpace (reg char *pktBuf) {

   static char cleanPkt[MAXPKT+1];
   reg char *cleanBuf;


   cleanBuf = cleanPkt;
   while (*pktBuf) {
      if (!isspace(*pktBuf)) *cleanBuf++ = *pktBuf;
      pktBuf++;
   }
   *cleanBuf = '\0';

   return cleanPkt;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Decode the ARP data.							    */
/*									    */
/* This function and showRarp() could be merged into one.		    */
/*									    */
/****==========------------------------------------------------==========****/

static char *showArp (char *p) {

   uint1 hLen;
   uint2 hType;
   uint2 op;
   uint1 pLen;
   uint2 pType;
   char sEtherAddr[ETHER_ADDRLEN+1];   // Sender Ethernet address
   char sEtherName[MAX_HOSTNAMELEN+1]; // Sender Ethernet name
   char sHostName[MAX_HOSTNAMELEN+1];  // Sender hostname
   char sIpAddr[IP_ADDRLEN+1];	       // Sender IP address
   char tEtherAddr[ETHER_ADDRLEN+1];   // Target Ethernet address
   char tEtherName[MAX_HOSTNAMELEN+1]; // Target Ethernet name
   char tHostName[MAX_HOSTNAMELEN+1];  // Target hostname
   char tIpAddr[IP_ADDRLEN+1];	       // Target IP address
   uint2 nSkipped;


   hType      = getWord(&p); nSkipped = sizeof(hType);
   pType      = getWord(&p); nSkipped = sizeof(pType);
   hLen	      = getByte(&p); nSkipped = sizeof(hLen);
   pLen	      = getByte(&p); nSkipped = sizeof(pLen);
   op	      = getWord(&p); nSkipped = sizeof(op);
   (void)strcpy(sEtherAddr, etherAddr(0, &p)); nSkipped += 6;
   (void)strcpy(sIpAddr, ipAddr(&p));	       nSkipped += 4;
   (void)strcpy(tEtherAddr, etherAddr(0, &p)); nSkipped += 6;
   (void)strcpy(tIpAddr, ipAddr(&p));	       nSkipped += 4;

   (void)strcpy(sEtherName, etherName(sEtherAddr, TRUE));
   (void)strcpy(tEtherName, etherName(tEtherAddr, TRUE));
   (void)strcpy(sHostName, hostName(sIpAddr, TRUE));
   (void)strcpy(tHostName, hostName(tIpAddr, TRUE));

   if (terseFlag) {
      switch (op) {
       case ARP_REQ:
	 printf(
	    "ARP:\t%s (%s) asks where is %s\n",
	    sHostName, sEtherName, tHostName
	 );
	 break;
       case ARP_RSP:
	 printf(
	    "ARP:\t%s says to %s it's at %s\n",
	    sHostName, tHostName, sEtherName
	 );
	 break;
      }
      return p;
   }

   if (compactFlag) {
      printf(
	 "ARP:\thtype=%s ptype=%s hlen=%d plen=%d op=%s\n",
	 hType == ARP_HW_ETHER? "Ethernet": unknown,
	 pType == ARP_PROTO_IP? "IP": unknown,
	 hLen, pLen, op == ARP_REQ? "request": "response"
      );
      printf(
	 "\tsender-MAC-addr=%s sender-IP-address=%s\n",
	 sEtherName, sHostName
      );
      printf(
	 "\ttarget-MAC-addr=%s target-IP-address=%s\n",
	 tEtherName, tHostName
      );
   }

   else {
      printf("ARP Header\n");
      printf(
	 "\tHardware Type:\t\t\t%s\n",
	 hType == ARP_HW_ETHER? "Ethernet": unknown
      );
      printf(
	 "\tProtocol Type:\t\t\t%s\n",
	 pType == ARP_PROTO_IP? "IP": unknown
      );
      printf("\tHardware Address Length:\t%d bytes\n", hLen);
      printf("\tProtocol Address Length:\t%d bytes\n", pLen);
      printf(
	 "\tOperation:\t\t\tARP %s\n",
	 op == ARP_REQ? "request": "response"
      );
      printf("\tSender Hardware Address:\t%s", sEtherAddr);
      if (!noEtherNames) printf(" (%s)", etherName(sEtherAddr, FALSE));
      printf("\n\tSender IP Address:\t\t%s", sIpAddr);
      if (!noHostNames) printf(" (%s)", hostName(sIpAddr, FALSE));
      printf("\n\tTarget Hardware Address:\t%s", tEtherAddr);
      if (!noEtherNames) printf(" (%s)", etherName(tEtherAddr, FALSE));
      printf("\n\tTarget IP Address:\t\t%s", tIpAddr);
      if (!noHostNames) printf(" (%s)", hostName(tIpAddr, FALSE));
      putchar('\n');
   }

   return p;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Decode the TCP/UDP data.						    */
/*									    */
/****==========------------------------------------------------==========****/

static char *showData (char *p) {

   uint1 byte;
   int col;
   char *descr;


   if (terseFlag || compactFlag)
      printf("DATA:\t");
   else {
      switch (proto) {
       case TCP:  descr = "TCP";   break;
       case UDP:  descr = "UDP";   break;
       case ICMP: descr = "ICMP";  break;
       default:	  descr = unknown; break;
      }
      printf("%s Data\n\t", descr);
   }
   if (noDataFlag) {
      uint2 ndatabytes = dataLen;
      dataLen = 0;
      printf("%d bytes\n", ndatabytes);
      return skip(p, ndatabytes);
   }

   if (dataLen == 0) {
      printf("<No data>\n");
      return p;
   }

   for (col = 1; dataLen > 0; dataLen--, col++) {
      byte = getByte(&p);
      /* mr990204 Don't display the last character if it's a newline, as    */
      /* this is done by us just after the loop terminates.		    */
      if (byte == '\n')
	 if (dataLen == 1)
	    continue;
	 else {
	    putchar('\n');
	    byte = '\t';
	    col = 0;
	 }
      elif (!bFlag && col > pageWidth) {
	 printf("%s\n\t", sbFlag? "<br>": "");
	 col = 1;
      }
      /* mr990204 Include '\r' in list. */
      if (byte != '\t' && byte != '\n' && byte != '\r' && !isprint(byte))
	 byte = '.';
      putchar(byte);
   }
   putchar('\n');

   return p;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Decode the packet header.						    */
/*									    */
/****==========------------------------------------------------==========****/

static char *showHdr (char *p) {

   char eFrom[ETHER_ADDRLEN+1];	       /* Source Ethernet address	    */
   char eFromName[MAX_HOSTNAMELEN+1];  // Sender Ethernet name
   char eTo[ETHER_ADDRLEN+1];	       /* Destination Ethernet address	    */
   char eToName[MAX_HOSTNAMELEN+1];    // Target Ethernet name
   char eType[20];		       /* Ethernet type (decoded to ASCII)  */
   char ifName[ETHER_ADDRLEN+1];       /* This field may be present	    */
   char pktType[20];		       /* This field may be present	    */
   static double prevTime;	       // Timestamp of previous packet
   char time[16];		       /* Packet timestamp		    */


   if (ppFlag) {
      (void)sscanf(p, "%s", time);
      etherType = ETHER_PROTO_IP;      /* tcpdump doesn't supply link type  */
      if (!noTimestamp)
	 if (terseFlag || compactFlag)
	    printf("TIME:\t%s%s\n", time, deltaTime(&prevTime, time));
	 else
	    printf(
	       "\tTimestamp:\t\t\t%s%s\n", time, deltaTime(&prevTime, time)
	    );
      return getPkt();
   }

   /* mr000114 The interface name and an additional field may be present.   */
   /* Let's assume they are to begin with.				    */
   (void)sscanf(p, "%s %s %s %s %s %s",
		time, ifName, pktType, eFrom, eTo, eType);
   if (strlen(ifName) >= ETHER_MINADDRLEN) {
      /* The interface name and other field are not present.		    */
      (void)strcpy(eType, eFrom);
      (void)strcpy(eTo, pktType);
      (void)strcpy(eFrom, ifName);
   }
   (void)etherProto(eType, &etherType);

   (void)strcpy(eFrom, etherAddr(eFrom, 0));
   (void)strcpy(eFromName, etherName(eFrom, TRUE));
   (void)strcpy(eTo, etherAddr(eTo, 0));
   (void)strcpy(eToName, etherName(eTo, TRUE));

   if (!noTimestamp)
      if (terseFlag || compactFlag)
	 printf("TIME:\t%s%s\n", time, deltaTime(&prevTime, time));
      elif (verboseFlag)
	 printf("\tTimestamp:\t\t\t%s%s\n", time, deltaTime(&prevTime, time));

   if (!noLinkFlag) {
      if (compactFlag)
	 printf(
	    "LINK:\t%s -> %s type=%s\n",
	    eFromName, eToName, etherProto(eType, 0)
	 );
      elif (verboseFlag) {
	 printf("\tSource Ethernet Address:\t%s", eFrom);
	 if (!noEtherNames) printf(" (%s)", etherName(eFrom, FALSE));
	 printf("\n\tDestination Ethernet Address:\t%s", eTo);
	 if (!noEtherNames) printf(" (%s)", etherName(eTo, FALSE));
	 printf("\n\tEncapsulated Protocol:\t\t%s\n", etherProto(eType, 0));
      }
   }

   return getPkt();

}


/****==========------------------------------------------------==========****/
/*									    */
/* Decode the ICMP header.						    */
/*									    */
/****==========------------------------------------------------==========****/

static char *showIcmp (char *p) {

   uint2 cksum;
   uint1 code;
   char *extraInfo;
   char *msgType;
   uint2 nSkipped;
   uint1 type;
   char *why;


   type	 = getByte(&p); nSkipped  = sizeof(type);
   code	 = getByte(&p); nSkipped += sizeof(code);
   cksum = getWord(&p); nSkipped += sizeof(cksum);

   msgType = icmpType(type);
   why = icmpCode(type, code);
   extraInfo = icmpExtras(type, code, &p, &nSkipped);

   /* The length of the ICMP packet isn't recorded in the packet itself.    */
   dataLen -= nSkipped;

   if (terseFlag) {
      printf(
	 "ICMP:\t%s -> %s %s%s%s\n",
	 sHostName, dHostName, msgType, why? " because ": "", why? why: ""
      );
      if (extraInfo) printf("\t%s\n", extraInfo);
      return p;			       /* Header is read; nothing to skip   */
   }

   if (compactFlag) {
      printf(
	 "ICMP:\t%s%s%s cksum=%04X\n",
	 msgType, why? " because ": "", why? why: "", cksum
      );
      if (extraInfo) printf("\t%s\n", extraInfo);
   }
   else {
      printf("ICMP Header\n");
      printf(
	 "\tType:\t\t\t\t%s%s%s\n",
	 msgType, why? "\n\tBecause:\t\t\t": "", why? why: ""
      );
      if (extraInfo) printf("\tAdditional Information:\t\t%s\n", extraInfo);
      printf("\tChecksum:\t\t\t0x%04X\n", cksum);
   }

   return p;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Decode the IP header.						    */
/*									    */
/****==========------------------------------------------------==========****/

static char *showIp (char *p) {

   uint2 cksum;
   uint2 dgramLen;
   uint2 flags;
   uint2 hLen;
   uint2 id;
   uint2 nSkipped;
   uint1 servType;
   uint1 ttl;
   uint1 ver;


   ver = getByte(&p); nSkipped	= sizeof(ver);
   if ((ver & 0xF0) != 0x40) {
      if (compactFlag) printf("IP:\tnot v4\n");
      else
	 printf(
	    "IP Header\n\t<Not an IPv4 datagram (ver=%d)>\n",
	    (ver & 0xF0) >> 4
	 );
      nextPkt();
   }
   servType = getByte(&p);	  nSkipped += sizeof(servType);
   dgramLen = getWord(&p);	  nSkipped += sizeof(dgramLen);
   id	    = getWord(&p);	  nSkipped += sizeof(id);
   flags    = getWord(&p);	  nSkipped += sizeof(flags);
   ttl	    = getByte(&p);	  nSkipped += sizeof(ttl);
   proto    = getByte(&p);	  nSkipped += sizeof(proto);
   cksum    = getWord(&p);	  nSkipped += sizeof(cksum);
   (void)strcpy(sIp, ipAddr(&p)); nSkipped += 4;
   (void)strcpy(dIp, ipAddr(&p)); nSkipped += 4;
   hLen	    = (ver & 0x0F) * 4;
   dataLen  = dgramLen - hLen;

   (void)strcpy(sHostName, hostName(sIp, TRUE));
   (void)strcpy(dHostName, hostName(dIp, TRUE));

   if (noIpFlag) return skip(p, hLen - nSkipped);

   printf("%s", compactFlag? "	IP:\t": "IP Header\n");

   if (compactFlag) {
      printf(
	 "%s -> %s\n\thlen=%d TOS=%02X dgramlen=%d id=%04X\n",
	 sHostName, dHostName, hLen, (uint2)servType, dgramLen, id
      );
      printf(
	 "\tMF/DF=%s/%s frag=%d TTL=%d proto=%s cksum=%04X\n",
	 (flags & MF) == MF? "1": "0", (flags & DF) == DF? "1": "0",
	 flags & FRAGOFF, ttl, ipProto(proto), cksum
      );
   }

   else {
      printf("\tVersion:\t\t\t4\n\tHeader Length:\t\t\t%d bytes\n", hLen);
      printf("\tService Type:\t\t\t0x%02X\n", (uint2)servType);
      printf("\tDatagram Length:\t\t%d bytes\n", dgramLen);
      printf("\tIdentification:\t\t\t0x%04X\n", id);
      printf(
	 "\tFlags:\t\t\t\tMF=%s DF=%s\n",
	 (flags & MF) == MF? on: off, (flags & DF) == DF? on_e: off_e
      );
      printf("\tFragment Offset:\t\t%d\n", flags & FRAGOFF);
      printf("\tTTL:\t\t\t\t%d\n", ttl);
      printf("\tEncapsulated Protocol:\t\t%s\n", ipProto(proto));
      printf("\tHeader Checksum:\t\t0x%04X\n", cksum);
      printf("\tSource IP Address:\t\t%s", sIp);
      if (!noHostNames) printf(" (%s)", hostName(sIp, FALSE));
      printf("\n\tDestination IP Address:\t\t%s", dIp);
      if (!noHostNames) printf(" (%s)", hostName(dIp, FALSE));
      putchar('\n');
   }

   if (hLen > IPHDRLEN) {
      if (!compactFlag) printf("\t<Options not displayed>\n");
      p = skip(p, hLen - IPHDRLEN);
   }

   return p;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Decode the packet chunk in the buffer.				    */
/*									    */
/****==========------------------------------------------------==========****/

static void showPkt (reg char *p) {

   char *warnMsg = "<*** No decode support for encapsulated protocol ***>";
   char *warnMsg2 = "<*** No decode support for encap protocol in IPIP packet ***>";


   prSep();
   printf("Packet %d\n", ++nPktsShown);

   p = showHdr(p);

   switch (etherType) {
    case ETHER_PROTO_ARP:
      p = showArp(p);
      break;
    case ETHER_PROTO_RARP:
      p = showRarp(p);
      break;
    case ETHER_PROTO_IP:
      p = showIp(p);
      switch (proto) {
       case TCP:
	 p = showTcp(p);
	 p = showData(p);
	 break;
       case UDP:
	 p = showUdp(p);
	 p = showData(p);
	 break;
       case ICMP:
	 p = showIcmp(p);
	 p = showData(p);
	 break;
       // IPIP decode support by M. Nowlin (mike@argos.org) 20000321
       case IPIP:
	 p = showIp(p);
	 switch(proto) {
	  case TCP:
	    p = showTcp(p);
	    p = showData(p);
	    break;
	  case UDP:
	    p = showUdp(p);
	    p = showData(p);
	    break;
	  case ICMP:
	    p = showIcmp(p);
	    p = showData(p);
	    break;
	  default:
	    printf("\t%s\n", warnMsg2);
	    nextPkt();
	    break;
	 }
	 break;
       default:
	 printf("\t%s\n", warnMsg);
	 nextPkt();		       /* Doesn't return		    */
      }
      break;
    default:
      if (!terseFlag) printf("\t%s\n", warnMsg);
      nextPkt();
      break;
   }

   /* "tcpdump" sometimes displays data at the end of a packet which, given */
   /* the recorded Datagram Length, don't belong to the packet.		    */
   if (*p) {
      if (sFlag) printf("\t<*** Spurious data at end: \"%s\" ***>\n", p);
      nextPkt();
   }
   /* Note that if getPkt() returns here, then the line read isn't the	    */
   /* start of a new packet, i.e. there's spurious data.		    */
   if (p = getPkt()) {
      if (sFlag) printf("\t<*** Spurious data at end: \"%s\" ***>\n", p);
      nextPkt();
   }

}


/****==========------------------------------------------------==========****/
/*									    */
/* Decode the RARP data.						    */
/*									    */
/* This function and showArp() could be merged into one.		    */
/*									    */
/****==========------------------------------------------------==========****/

static char *showRarp (char *p) {

   uint1 hLen;
   uint2 hType;
   uint2 op;
   uint1 pLen;
   uint2 pType;
   char sEtherAddr[ETHER_ADDRLEN+1];   // Sender Ethernet address
   char sEtherName[MAX_HOSTNAMELEN+1]; // Sender Ethernet name
   char sHostName[MAX_HOSTNAMELEN+1];  // Sender hostname
   char sIpAddr[IP_ADDRLEN+1];	       // Sender IP address
   char tEtherAddr[ETHER_ADDRLEN+1];   // Target Ethernet address
   char tEtherName[MAX_HOSTNAMELEN+1]; // Target Ethernet name
   char tHostName[MAX_HOSTNAMELEN+1];  // Target hostname
   char tIpAddr[IP_ADDRLEN+1];	       // Target IP address
   uint2 nSkipped;


   hType      = getWord(&p); nSkipped = sizeof(hType);
   pType      = getWord(&p); nSkipped = sizeof(pType);
   hLen	      = getByte(&p); nSkipped = sizeof(hLen);
   pLen	      = getByte(&p); nSkipped = sizeof(pLen);
   op	      = getWord(&p); nSkipped = sizeof(op);
   (void)strcpy(sEtherAddr, etherAddr(0, &p)); nSkipped += 6;
   (void)strcpy(sIpAddr, ipAddr(&p));	       nSkipped += 4;
   (void)strcpy(tEtherAddr, etherAddr(0, &p)); nSkipped += 6;
   (void)strcpy(tIpAddr, ipAddr(&p));	       nSkipped += 4;

   (void)strcpy(sEtherName, etherName(sEtherAddr, TRUE));
   (void)strcpy(tEtherName, etherName(tEtherAddr, TRUE));
   (void)strcpy(sHostName, hostName(sIpAddr, TRUE));
   (void)strcpy(tHostName, hostName(tIpAddr, TRUE));

   if (terseFlag) {
      switch (op) {
       case RARP_REQ:
	 printf(
	    "RARP:\t%s asks for its %s address\n",
	    sEtherName, pType == ARP_PROTO_IP? "Ethernet": unknown
	 );
	 break;
       case RARP_RSP:
	 printf(
	    "RARP:\t%s says to %s its %s address is %s\n",
	    sHostName, tEtherName,
	    pType == ARP_PROTO_IP? "Ethernet": unknown, tHostName
	 );
	 break;
      }
      return p;
   }

   if (compactFlag) {
      printf(
	 "RARP:\thtype=%s ptype=%s hlen=%d plen=%d op=%s\n",
	 hType == ARP_HW_ETHER? "Ethernet": unknown,
	 pType == ARP_PROTO_IP? "IP": unknown,
	 hLen, pLen, op == RARP_REQ? "request": "response"
      );
      printf(
	 "\tsender-MAC-addr=%s sender-IP-address=%s\n",
	 sEtherName, sHostName
      );
      printf(
	 "\ttarget-MAC-addr=%s target-IP-address=%s\n",
	 tEtherName, tHostName
      );
   }

   else {
      printf("RARP Header\n");
      printf(
	 "\tHardware Type:\t\t\t%s\n",
	 hType == ARP_HW_ETHER? "Ethernet": unknown
      );
      printf(
	 "\tProtocol Type:\t\t\t%s\n",
	 pType == ARP_PROTO_IP? "IP": unknown
      );
      printf("\tHardware Address Length:\t%d bytes\n", hLen);
      printf("\tProtocol Address Length:\t%d bytes\n", pLen);
      printf(
	 "\tOperation:\t\t\tRARP %s\n",
	 op == RARP_REQ? "request": "response"
      );
      printf("\tSender Hardware Address:\t%s", sEtherAddr);
      if (!noEtherNames) printf(" (%s)", etherName(sEtherAddr, FALSE));
      printf("\n\tSender IP Address:\t\t%s", sIpAddr);
      if (!noHostNames) printf(" (%s)", hostName(sIpAddr, FALSE));
      printf("\n\tTarget Hardware Address:\t%s", tEtherAddr);
      if (!noEtherNames) printf(" (%s)", etherName(tEtherAddr, FALSE));
      printf("\n\tTarget IP Address:\t\t%s", tIpAddr);
      if (!noHostNames) printf(" (%s)", hostName(tIpAddr, FALSE));
      putchar('\n');
   }

   return p;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Decode the TCP header.						    */
/*									    */
/****==========------------------------------------------------==========****/

static char *showTcp (char *p) {

   uint4 ack;
   uint2 advert;
   uint2 cksum;
   uint2 dPort;
   char dPortName[MAX_PORTNAMELEN+1];
   uint4 expect;
   uint2 flags;
   uint2 hLen;
   uint2 nSkipped;
   uint4 seq;
   uint2 sPort;
   char sPortName[MAX_PORTNAMELEN+1];
   uint2 urgPtr;


   sPort  = getWord(&p);     nSkipped  = sizeof(sPort);
   dPort  = getWord(&p);     nSkipped += sizeof(dPort);
   seq	  = getLongWord(&p); nSkipped += sizeof(seq);
   ack	  = getLongWord(&p); nSkipped += sizeof(ack);
   flags  = getWord(&p);     nSkipped += sizeof(flags);
   advert = getWord(&p);     nSkipped += sizeof(advert);
   cksum  = getWord(&p);     nSkipped += sizeof(cksum);
   urgPtr = getWord(&p);     nSkipped += sizeof(urgPtr);

   hLen = (flags >> 12 & 0x0F) * 4;
   dataLen -= hLen;

   (void)strcpy(sPortName, portName(sPort, "tcp", TRUE));
   (void)strcpy(dPortName, portName(dPort, "tcp", TRUE));

   if (terseFlag) {
      printf(
	 "TCP:\t%s.%s -> %s.%s %s\n",
	 sHostName, sPortName, dHostName, dPortName, showTcpFlagsOn(flags)
      );
      return skip(p, hLen - nSkipped);
   }

   if (trackFlag) {
      expect = seq + dataLen;
      if ((flags & SYN) == SYN || (flags & FIN) == FIN) expect++;
   }

   if (compactFlag) {
      printf(
	 " TCP:\tport %s -> %s\n\tseq=%010lu", sPortName, dPortName, seq
      );
      if (trackFlag) printf(" (expect=%010lu)", expect);
      printf(" ack=%010lu\n", ack);
      printf(
	 "\thlen=%d (data=%u) UAPRSF=%s",
	 hLen, dataLen, showTcpFlagsAsBits(flags)
      );
      printf(" wnd=%d cksum=%04X urg=%d\n", advert, cksum, urgPtr);
   }

   else {
      printf("TCP Header\n");
      printf("\tSource Port:\t\t\t%d", sPort);
      if (!noPortNames) printf(" (%s)", portName(sPort, "tcp", FALSE));
      printf("\n\tDestination Port:\t\t%d", dPort);
      if (!noPortNames) printf(" (%s)", portName(dPort, "tcp", FALSE));
      printf("\n\tSequence Number:\t\t%010lu\n", seq);
      if (trackFlag) printf("\tExpect peer ACK:\t\t%010lu\n", expect);
      printf("\tAcknowledgement Number:\t\t%010lu\n", ack);
      printf("\tHeader Length:\t\t\t%d bytes (data=%u)\n", hLen, dataLen);
      printf(
	 "\tFlags:%s%s%s%s%s%s\n%s%s%s%s%s%s\n",
	 "\t\t\t\tURG=",   (flags & URG) == URG? on: off,
	 " ACK=",	   (flags & ACK) == ACK? on: off,
	 " PSH=",	   (flags & PSH) == PSH? on_e: off_e,
	 "\t\t\t\t\tRST=", (flags & RST) == RST? on: off,
	 " SYN=",	   (flags & SYN) == SYN? on: off,
	 " FIN=",	   (flags & FIN) == FIN? on_e: off_e
      );
      printf("\tWindow Advertisement:\t\t%d bytes\n", advert);
      printf("\tChecksum:\t\t\t0x%04X\n", cksum);
      printf("\tUrgent Pointer:\t\t\t%d\n", urgPtr);
   }

   if (hLen > TCPHDRLEN) {
      if (!compactFlag) printf("\t<Options not displayed>\n");
      p = skip(p, hLen - TCPHDRLEN);
   }

   return p;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Show, in a string, the values of the TCP flags, as a set of bits, in the */
/* order URG, ACK, PSH, RST, SYN, FIN (i.e. their order in the header).	    */
/* The string is overwritten on each call.				    */
/*									    */
/* mr990211								    */
/*									    */
/****==========------------------------------------------------==========****/

static char *showTcpFlagsAsBits (uint2 flags) {
   static char bits[NTCPFLAGS+1];

   (void)strcpy(bits, (flags & URG) == URG? "1": "0");
   (void)strcat(bits, (flags & ACK) == ACK? "1": "0");
   (void)strcat(bits, (flags & PSH) == PSH? "1": "0");
   (void)strcat(bits, (flags & RST) == RST? "1": "0");
   (void)strcat(bits, (flags & SYN) == SYN? "1": "0");
   (void)strcat(bits, (flags & FIN) == FIN? "1": "0");
   return bits;
}


/****==========------------------------------------------------==========****/
/*									    */
/* Show, in a string, which TCP flags are on, in an order which brings	    */
/* 'related' flags close together.					    */
/* The string is overwritten on each call.				    */
/*									    */
/* mr990225								    */
/*									    */
/****==========------------------------------------------------==========****/

static char *showTcpFlagsOn (uint2 flags) {
   /* Each flag identifier is three chars long. */
   static char flagsOn[NTCPFLAGS*3+1];

   (void)strcpy(flagsOn, (flags & SYN) == SYN? "Syn": "");
   if ((flags & FIN) == FIN) (void)strcat(flagsOn, "Fin");
   if ((flags & RST) == RST) (void)strcat(flagsOn, "Rst");
   if ((flags & ACK) == ACK) (void)strcat(flagsOn, "Ack");
   if ((flags & PSH) == PSH) (void)strcat(flagsOn, "Psh");
   if ((flags & URG) == URG) (void)strcat(flagsOn, "Urg");
   return flagsOn;
}


/****==========------------------------------------------------==========****/
/*									    */
/* Decode the UDP header.						    */
/*									    */
/****==========------------------------------------------------==========****/

static char *showUdp (char *p) {

   uint2 cksum;
   uint2 dgramLen;
   uint2 dPort;
   char dPortName[MAX_PORTNAMELEN+1];
   uint2 nSkipped;
   uint2 sPort;
   char sPortName[MAX_PORTNAMELEN+1];


   sPort    = getWord(&p); nSkipped  = sizeof(sPort);
   dPort    = getWord(&p); nSkipped += sizeof(dPort);
   dgramLen = getWord(&p); nSkipped += sizeof(dgramLen);
   cksum    = getWord(&p); nSkipped += sizeof(cksum);

   /* The size of the IP data field should equal the UDP packet length.	    */
   if (dataLen != dgramLen) {
      printf("\t<*** Packet length corrupt ***>\n");
      nextPkt();		       /* Doesn't return		    */
   }
   dataLen -= UDPHDRLEN;

   (void)strcpy(sPortName, portName(sPort, "udp", TRUE));
   (void)strcpy(dPortName, portName(dPort, "udp", TRUE));

   if (terseFlag) {
      printf(
	 "UDP:\t%s.%s -> %s.%s\n",
	 sHostName, sPortName, dHostName, dPortName
      );
      return p;			       /* Header is read; nothing to skip   */
   }

   if (compactFlag)
      printf(
	 " UDP:\tport %s -> %s hdr=%u data=%u\n",
	 sPortName, dPortName, UDPHDRLEN, dataLen
      );
   else {
      printf("UDP Header\n");
      printf("\tSource Port:\t\t\t%d", sPort);
      if (!noPortNames) printf(" (%s)", portName(sPort, "udp", FALSE));
      printf("\n\tDestination Port:\t\t%d", dPort);
      if (!noPortNames) printf(" (%s)", portName(dPort, "udp", FALSE));
      printf(
	 "\n\tDatagram Length:\t\t%u bytes (Header=%u, Data=%u)\n",
	 dgramLen, UDPHDRLEN, dataLen
      );
      printf("\tChecksum:\t\t\t0x%04X\n", cksum);
   }

   return p;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Skip over un-interesting bytes.					    */
/*									    */
/****==========------------------------------------------------==========****/

static char *skip (char *p, uint2 nBytes) {

   for ( ; nBytes > 0; nBytes--) (void)getByte(&p);
   return p;

}


/****==========------------------------------------------------==========****/
/*									    */
/* Give a summary of usage.						    */
/*									    */
/****==========------------------------------------------------==========****/

static void usage () {

#if !defined(MAY_NOT_MODIFY)
   printf("\nCopyright (c) 1996, 1997, 1998, 1999, 2000 Michael Ryan.  ");
   printf("All rights reserved.\n");
   printf("ftp://ftp.NetworX.ie/pub/net/tcpshow/\n");
   printf("mailto:Michael.Ryan@pobox.com\n\n");
#endif
   printf("tcpshow -- decode a tcpdump(1) savefile, giving a fully ");
   printf("decoded display of\n\t   Ethernet, ARP, RARP, IP, ICMP, ");
   printf("UDP and TCP headers and an ASCII\n\t	  display of the ");
   printf("application data.\n\n");
   printf("Version %4.2f\n\n", VERSION);
   printf("Usage: tcpshow [ options ... ] [ expr ]\n");
   printf("\nwhere options are as follows\n");
   printf("\t-b\t\tdo not break/wrap long lines\n");
   printf("\t-sb\t\tshow breaks (show where we broke a line)\n");
   printf("\t-w width\tset pagewidth to \"width\" columns ");
   printf("(used when -b isn't)\n");
   printf("\t-noLink\t\tdon't decode link header (Ethernet header)\n");
   printf("\t-noIp\t\tdon't decode IP header\n");
   printf("\t-noHostNames\tdon't map IP addresses to host names\n");
   printf("\t-fqdn\t\tshow host names as fully-qualified\n");
#if !defined(NOETHERNAMES)
   printf("\t-noEtherNames\tdon't map Ethernet addresses to host names\n");
#endif
   printf("\t-noPortNames\tdon't map port numbers to names\n");
   printf("\t-noData\t\tdon't show data (show headers only)\n");
   printf("\t-track\t\ttrack sequence numbers (show next-expected ACK)\n");
   printf("\t-terseHeaders\tshow only a minimal header decode (default)\n");
   printf("\t-compactHeaders\tshow header decode in compact format\n");
   printf("\t-verboseHeaders\tshow header decode in expanded format\n");
   printf("\t-cooked\t\tdon't run tcpdump to pre-process the input\n");
   printf("\t-pp\t\tpoint-to-point link (no Ethernet header available)\n");
   printf("\t-s\t\tdisplay hex dump of spurious data at packet-end\n");
   printf("\t-h\t\tdisplay this help summary\n\n");
   printf("expr is a tcpdump(1) expression, and is only valid when ");
   printf("the -cooked\noption is not used.\n\n");
   printf("Input is from stdin, ");
   printf("which must be a raw tcpdump(1) data file (savefile),\n");
   printf("unless the -cooked option is used, in which case stdin ");
   printf("must be in the\nformat produced by tcpdump -lenx.\n\n");
   printf("Output is to stdout.\n\n");
   printf("tcpdump(1) must be on your PATH unless -cooked is used.\n\n");

   exit(0);

}
